#include <iostream>

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netdb.h>
#include <errno.h>

#include "Server.hh"

// NOTE: 'maybee' too C-style ?

// need a serverError fun / class

Server::Server(char* port) : _port(port)
{
  buildHints();
  init(port);
}


Server::~Server()
{
}

//#ifdef DEBUG
void Server::printHints()
{
  printf("family: %i\n", _hints.ai_family);    /* Allow IPv4 or IPv6 */
  printf("socktype: %i\n", _hints.ai_socktype); /* Datagram socket */
  printf("flags: %i\n", _hints.ai_flags);    /* For wildcard IP address */
  printf("protocol: %i\n", _hints.ai_protocol);          /* Any protocol */
  printf("canonname: %s\n", _hints.ai_canonname);
//  printf("addr: %s\n", _hints.ai_addr);
//  printf("next: %s\n", _hints.ai_next);
}
//#endif


void Server::buildHints()
{
  memset(&_hints, 0, sizeof(struct addrinfo));
  _hints.ai_family = AF_UNSPEC;    /* Allow IPv4 or IPv6 */
  _hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */
  _hints.ai_flags = AI_PASSIVE;    /* For wildcard IP address */
  _hints.ai_protocol = 0;          /* Any protocol */
  _hints.ai_canonname = NULL;
  _hints.ai_addr = NULL;
  _hints.ai_next = NULL;
}


void Server::init(char* portString)
{
  if ((_s = getaddrinfo(NULL, portString, &_hints, &_result)))
  {
    fprintf(stderr, "getAddrInfo: %s", gai_strerror(_s));
    exit(EXIT_FAILURE);
  }

  /* getaddrinfo() returns a list of address structures.
     Try each address until we successfully bind(2).
     If socket(2) (or bind(2)) fails, we (close the socket
     and) try the next address. */

  for (_rp = _result; _rp; _rp = _rp->ai_next)
  {
    _sfd = socket(_rp->ai_family, _rp->ai_socktype, _rp->ai_protocol);
    if (_sfd == -1)
    {
      int tmp = errno; // saffer storage (errno cd be altered by another lib / syscall)
      fprintf(stderr, "socket error %i", tmp);
      continue;
    }

    if (bind(_sfd, _rp->ai_addr, _rp->ai_addrlen) == 0)
      break;                  /* Success */

    close(_sfd);
  }

  if (!_rp)
  {               /* No address succeeded */
    fprintf(stderr, "Could not bind\n");
    exit(EXIT_FAILURE);
  }

//#ifdef DEBUG
    printHints();
// endif

  freeaddrinfo(_result);           /* No longer needed */
}




void Server::run()
{
  /* Read datagrams and echo them back to sender */
  for (; ; )
  {
    _peerAddrLen = sizeof(struct sockaddr_storage);
    _nread = recvfrom(_sfd, _buf, SERVER_BUF_SIZE, 0,
		     (struct sockaddr *) &_peerAddr, &_peerAddrLen);
    if (_nread == -1) /* failed request */
    {
      #ifdef DEBUG
        fprintf(stderr, "failed request");
      #endif
      continue;
    }

    char host[NI_MAXHOST], service[NI_MAXSERV];
    _s = getnameinfo((struct sockaddr *) &_peerAddr,
		    _peerAddrLen, host, NI_MAXHOST,
		    service, NI_MAXSERV, NI_NUMERICSERV);
    if (!_s)
      printf("Received %ld bytes from %s:%s which are: %s\n",
			 (long) _nread, host, service, _buf);
    else
      fprintf(stderr, "getnameinfo: ");// + gai_strerror(_s));

    if (sendto(_sfd, _buf, _nread, 0,
	       (struct sockaddr *) &_peerAddr,
	       _peerAddrLen) != _nread)
    {
      fprintf(stderr, "Error sending response");
    }
  }
}
